#ifndef GEDGEE_CONSTANT_H
#define GEDGEE_CONSTANT_H

#include <cmath>

#include "common.h"

namespace gedgee {
    // vector128

    namespace constant {
        template <typename srcT, typename dstT>
        inline constexpr vector128 v128_cvt(vector128 vec) {
            vector128 output = {0};

            if constexpr (sizeof(dstT) > sizeof(vector128)) {
                static_assert(false, "the size of dst type is bigger than vector128.");
            }

            if constexpr (sizeof(srcT) > sizeof(vector128)) {
                static_assert(false, "the size of src type is bigger than vector128.");
            }

            srcT *src = reinterpret_cast<srcT*>(&vec);
            dstT *dst = reinterpret_cast<dstT*>(&output);


            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / _tool::max(sizeof(srcT), sizeof(dstT))); i ++) {
                dst[i] = src[i];
            }

            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_load(const T *p) {
            vector128 output;
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                T *dst = reinterpret_cast<T*>(&output);
                dst[i] = p[i];
            }
            return output;
        }

        template <typename T>
        inline constexpr void v128_store(const T *p, vector128 vec) {
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                T *src = reinterpret_cast<T*>(&vec);
                p[i] = src[i];
            }
        }

        template <typename T>
        inline constexpr vector128 v128_set(const T n) {
            vector128 output;
            reinterpret_cast<T*>(&output)[0] = n;
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_fill(const T n) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                dst[i] = n;
            }
            return output;
        }

        template <typename T>
        inline constexpr T v128_get(vector128 vec) {
            return reinterpret_cast<T*>(&vec)[0];
        }

        template <typename T>
        inline constexpr vector128 v128_add(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                dst[i] = src1[i] + src2[i];
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_sub(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                dst[i] = src1[i] - src2[i];
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_mul(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                dst[i] = src1[i] * src2[i];
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_div(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                dst[i] = src1[i] / src2[i];
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_max(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n1 = src1[i];
                const T n2 = src2[i];
                if constexpr (_tool::is_f32<T>()) {
                    dst[i] = fmaxf(n1, n2);
                } else if constexpr (_tool::is_f64<T>()) {
                    dst[i] = fmax(n1, n2);
                } else {
                    dst[i] = n1 >= n2 ? n1 : n2;
                }
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_min(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n1 = src1[i];
                const T n2 = src2[i];
                if constexpr (_tool::is_f32<T>()) {
                    dst[i] = fminf(n1, n2);
                } else if constexpr (_tool::is_f64<T>()) {
                    dst[i] = fmin(n1, n2);
                } else {
                    dst[i] = n1 <= n2 ? n1 : n2;
                }
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_and(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n1 = src1[i];
                const T n2 = src2[i];
                dst[i] = n1 & n2;
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_or(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n1 = src1[i];
                const T n2 = src2[i];
                dst[i] = n1 | n2;
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_xor(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n1 = src1[i];
                const T n2 = src2[i];
                dst[i] = n1 ^ n2;
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_andnot(vector128 vec1, vector128 vec2) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src1 = reinterpret_cast<T*>(&vec1);
            T *src2 = reinterpret_cast<T*>(&vec2);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n1 = src1[i];
                const T n2 = src2[i];
                dst[i] = (~n1) & n2;
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_sll(vector128 vec, uint32_t count) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src = reinterpret_cast<T*>(&vec);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n = src[i];
                dst[i] = n << count;
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_srl(vector128 vec, uint32_t count) {
            if constexpr (_tool::is_u<T>()) {
                vector128 output;
                T *dst = reinterpret_cast<T*>(&output);
                T *src = reinterpret_cast<T*>(&vec);
                GEDGEE_UNROLL
                for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                    const T n = src[i];
                    dst[i] = n >> count;
                }
                return output;
            } else {
                static_assert(false, "srl only support unsigned integer");
            }
        }

        template <typename T>
        inline constexpr vector128 v128_sra(vector128 vec, uint32_t count) {
            if constexpr (_tool::is_i<T>()) {
                vector128 output;
                T *dst = reinterpret_cast<T*>(&output);
                T *src = reinterpret_cast<T*>(&vec);
                GEDGEE_UNROLL
                for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                    const T n = src[i];
                    dst[i] = n >> count;
                }
                return output;
            } else {
                static_assert(false, "sra only support signed integer");
            }
        }

        template <typename T>
        inline constexpr vector128 v128_abs(vector128 vec) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src = reinterpret_cast<T*>(&vec);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n = src[i];
                dst[i] = n >= 0 ? n : -n;
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_neg(vector128 vec) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src = reinterpret_cast<T*>(&vec);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n = src[i];
                dst[i] = -n;
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_sqrt(vector128 vec) {
             vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src = reinterpret_cast<T*>(&vec);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n = src[i];
                if constexpr (_tool::is_f32<T>()) {
                    dst[i] = sqrtf(n);
                } else if constexpr (_tool::is_f64<T>()) {
                    dst[i] = sqrt(n);
                } else if constexpr (_tool::is_i<T>() || _tool::is_u<T>()) {
                    dst[i] = sqrt(n);
                } else {
                    static_assert(false, "sqrt not support this type");
                }
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_rsqrt(vector128 vec) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src = reinterpret_cast<T*>(&vec);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n = src[i];
                if constexpr (_tool::is_f32<T>()) {
                    dst[i] = 1.0f / sqrtf(n);
                } else if constexpr (_tool::is_f64<T>()) {
                    dst[i] = 1.0 / sqrt(n);
                } else if constexpr (_tool::is_i<T>() || _tool::is_u<T>()) {
                    dst[i] = 1.0 / sqrt(n);
                } else {
                    static_assert(false, "rsqrt not support this type");
                }
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_rcp(vector128 vec) {
            if constexpr (!_tool::is_f<T>()) {
                static_assert(false, "Only float type support rcp.");
            }

            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src = reinterpret_cast<T*>(&vec);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n = src[i];
                dst[i] = 1 / n;
            }
            return output;
        }

        template <typename T>
        inline vector128 v128_fma(vector128 vec, vector128 vmul, vector128 vadd) {
            vector128 output;
            T *dst = reinterpret_cast<T*>(&output);
            T *src = reinterpret_cast<T*>(&vec);
            T *smul = reinterpret_cast<T*>(&vmul);
            T *sadd = reinterpret_cast<T*>(&vadd);
            GEDGEE_UNROLL
            for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                const T n = src[i];
                const T m = smul[i];
                const T a = sadd[i];
                dst[i] = n * m + a;
            }
            return output;
        }

        template <typename T>
        inline constexpr vector128 v128_round(vector128 vec) {
            if constexpr (_tool::is_u<T>() || _tool::is_i<T>()) {
                return vec;
            } else {
                vector128 output;
                T *dst = reinterpret_cast<T*>(&output);
                T *src = reinterpret_cast<T*>(&vec);
                GEDGEE_UNROLL
                for (int i = 0; i < (sizeof(vector128) / sizeof(T)); i ++) {
                    T n = src[i];
                    if constexpr (_tool::is_f32<T>()) {
                        dst[i] = roundf(n);
                    } else if constexpr (_tool::is_f64<T>()) {
                        dst[i] = round(n);
                    } else {
                        static_assert(false, "round not support this type");
                    }
                }
                return output;
            }
        }
    }
}

#endif
